<?php
if(!class_exists('XAnnotationClassFactory', false)){
	require_once(XFRAMEWORK_DIR.'/core/factory/XAnnotationClassFactory.php');
}
class XAnnotationAOPFactory extends XAnnotationClassFactory{
	private static $instance = null;
	
	private function __construct() {
	}
	
	/** 
	 * @return XAnnotationAOPFactory
	 */		
	public static function singleton() {
		if (!self::$instance) {
			self::$instance = new self;
		}
		return self::$instance;
	}
	
	/**
	 * @return object
	 */
	function get($class, $scope = null, $args = array()){
		return new XClassProxy($this, parent::get($class, $scope, $args));
	}
	
	public function invokeArgs($object, $method, $args = array()){		
		$context = XAOPContext::singleton();
		$class = get_class($object);
		$return = null;
		try{
			//Before
			$this->weaveBeforeAdvices($context, $class, $object, $method, $args);
			//Around
			$return = $this->weaveAroundAdvices($context, $class, $object, $method, $args);
			//AfterReturing
			$this->weaveAfterReturingAdvices($context, $class, $object, $method, $return);
		}catch(Exception $e){
			//AfterThrowing
			$this->weaveAfterThrowingAdvices($context, $class, $object, $method, $e);
			XEventContext::singleton()->publish(new XLoggingEvent($object, ERROR, $e->getMessage(), $e));
		}
		//After
		$this->weaveAfterAdvices($context, $class, $object, $method);
		return $return;
	}
	
	protected function weaveBeforeAdvices($context, $class, $object, $method, $args){
		$beforeAdvices = $context->beforeAdvices();
		foreach($beforeAdvices as $beforeAdvice){
			$pointcut = $context->pointcut($beforeAdvice->getPointcutName());
			if(is_null($pointcut)
				|| !$pointcut->match($class, $method)){
				continue;
			}
			parent::invokeArgs(parent::get($beforeAdvice->getSource()), $beforeAdvice->getMethod(), array($object, $args));
		}
	}	
	
	protected function weaveAroundAdvices($context, $class, $object, $method, $args){
		$return = null;
		$invoked = false;
		$aroundAdvices = $context->aroundAdvices();
		foreach($aroundAdvices as $aroundAdvice){
			$pointcut = $context->pointcut($aroundAdvice->getPointcutName());
			if(is_null($pointcut)
				|| !$pointcut->match($class, $method)){
				continue;
			}
			$jointPoint = new XProceedingJoinPoint(XAnnotationClassFactory::singleton(), $object, $method, $args);
			$return = parent::invokeArgs(parent::get($aroundAdvice->getSource()), $aroundAdvice->getMethod(), array($jointPoint));
			$invoked = true;
			break;
		}
		if(!$invoked){			
			$return = parent::invokeArgs($object, $method, $args);
		}
		if(is_object($return)
			&& !XClassLoader::loader(get_class($return))->hasAncestor('XClassProxy')){
			return new XClassProxy($this, $return);
		}elseif(is_array($return)){
			foreach($return as $key => $value){
				if(is_object($value)
					&& !XClassLoader::loader(get_class($value))->hasAncestor('XClassProxy')){
					$return[$key] = new XClassProxy($this, $value);
				}
			}
		}
		return $return;
	}
		
	protected function weaveAfterReturingAdvices($context, $class, $object, $method, $return){
		$afterReturningAdvices = $context->afterReturningAdvices();
		foreach($afterReturningAdvices as $afterReturningAdvice){
			$pointcut = $context->pointcut($afterReturningAdvice->getPointcutName());
			if(is_null($pointcut)
				|| !$pointcut->match($class, $method)){
				continue;
			}
			parent::invokeArgs(parent::get($afterReturningAdvice->getSource()), $afterReturningAdvice->getMethod(), array($object, $return));
		}
	}
	
	protected function weaveAfterThrowingAdvices($context, $class, $object, $method, $e){
		$afterThrowingAdvices = $context->afterThrowingAdvices();
		foreach($afterThrowingAdvices as $afterThrowingAdvice){
			$pointcut = $context->pointcut($afterThrowingAdvice->getPointcutName());
			if(is_null($pointcut)
				|| !$pointcut->match($class, $method)){
				continue;
			}
			parent::invokeArgs(parent::get($afterThrowingAdvice->getSource()), $afterThrowingAdvice->getMethod(), array($object, $e));
		}
	}	
	
	protected function weaveAfterAdvices($context, $class, $object, $method){
		$afterAdvices = $context->afterAdvices();
		foreach($afterAdvices as $afterAdvice){
			$pointcut = $context->pointcut($afterAdvice->getPointcutName());
			if(is_null($pointcut)
				|| !$pointcut->match($class, $method)){
				continue;
			}
			parent::invokeArgs(parent::get($afterAdvice->getSource()), $afterAdvice->getMethod(), array($object));
		}		
	}
}
?>